package org.xmppbot.net;

import java.io.IOException;
import java.io.StringReader;

import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.net.MXParser;
import org.jivesoftware.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Message;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
import org.xmpp.packet.Roster;
import org.xmpp.packet.StreamError;

import org.xmppbot.BotManager;
import org.xmppbot.Connection;
import org.xmppbot.StreamID;
import org.xmppbot.server.RemoteServerManager;
import org.xmppbot.session.LocalSession;
import org.xmppbot.session.Session;


/**
 * Обрабатывает входящие станзы
 * @author stas
 *
 */
abstract class StanzaHandlerImpl {
    /**
     * The utf-8 charset for decoding and encoding Jabber packet streams.
     */
    protected static String CHARSET = "UTF-8";

	// Через данное соединение будут отправляться ответы
	protected Connection _connection;
	
	protected LocalSession _session;
	/*
	 * true - если объект сессии (LocalSession) создан. Даже если авторизация не пройдена.
	 * false - в противном случае
	 */
	protected boolean _sessionCreated;
    // Flag that indicates that the client requested to use TLS and TLS has been negotiated. Once the
    // client sent a new initial stream header the value will return to false.
    private boolean startedTLS = false;
    // Flag that indicates that the client requested to be authenticated. Once the
    // authentication process is over the value will return to false.
    private boolean startedSASL = false;

    /**
     * Router used to route incoming packets to the correct channels.
     */
    private PacketRouter router;

    /**
     * SASL status based on the last SASL interaction
     */
    private SASLAuthentication.Status saslStatus;
  
    // DANIELE: Indicate if a stream:stream is arrived to complete compression
    private boolean waitingCompressionACK = false;

    
	public StanzaHandlerImpl(Connection connection) {
		_connection = connection;
	}

    /**
	 * Основной метод в обработчике станз
	 * @param stanza - полученная станза в форме строки
	 * @param reader - парсер станзы
	 * @throws Exception
	 */
	public void process(String stanza, XMPPPacketReader reader) throws Exception{
		_logger.debug("process stanza: "+stanza);
		boolean isThisStreamHeader = stanza.startsWith("<stream:stream");
		if (!_sessionCreated || isThisStreamHeader) {
			if (!_sessionCreated){

				_sessionCreated = true;
				MXParser parser = reader.getXPPParser();
				parser.setInput(new StringReader(stanza));
				createSession(parser);

			}else if (startedTLS) {
				startedTLS = false;
	            tlsNegotiated();
	        }else if (startedSASL && saslStatus == SASLAuthentication.Status.authenticated) {
	        	startedSASL = false;
	        	saslSuccessful();
	        }else if (waitingCompressionACK) {
	        	waitingCompressionACK = false;
	        	compressionSuccessful();
	        }
	        return;
			
	        

	        /**
	         * Этот блок относиться к Server Dialback.
	         * Скоро уберу его
	         */
//	        StreamID streamID = null;
//	        StringBuffer responseSB = null;
			/*
			 * Server Dialback
			 * 
			 * @see createIncomingSession метод класса org.jivesoftware.openfire.server.ServerDialback
			 * @see http://xmpp.org/rfcs/rfc3920.html#dialback
			 */
//			if ("jabber:server:dialback".equals(parser.getNamespace("db"))) {
//				_logger.debug("Запрос на начало Server Dialback");
//				streamID = StreamIDFactory.getInstance().createStreamID();
//				responseSB = new StringBuffer();
//
//				responseSB.append("<stream:stream");
//	            responseSB.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
//	            responseSB.append(" xmlns=\"jabber:server\" xmlns:db=\"jabber:server:dialback\"");
//				// Нужно добавить генератор sessionID
//	            responseSB.append(" id=\"");
//	            responseSB.append(streamID.getID());
//	            responseSB.append("\">");
//
//	            _connection.deliver(responseSB.toString());
//
//	            try{
//	            	Element doc = reader.parseDocument().getRootElement();
//	            	if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) {
//	            		if (validateRemoteDomain(doc, streamID)) {
//
//	            		}
////	            	}else if ("db".equals(doc.getNamespacePrefix()) && "verify".equals(doc.getName())) {
//	            		/*
//	            		 * Я думаю пока данный бот сервер не будет выступать в роли Originating Server, т.е. не будет
//	            		 * соединяться с другими серверами
//	            		 */
//	            		//verifyReceivedKey(doc, connection);
//	            	}else{
//	            		_connection.deliver(new StreamError(StreamError.Condition.invalid_xml).toXML());
//	            		_connection.close();
//	            	}
//	            }catch (Exception e){
//	            	_logger.error("An error occured while creating a server session", e);
//	            }
//			}
			
			/**
			 * END: Server Dialback
			 */
		}
        // Verify if end of stream was requested
        if (stanza.equals("</stream:stream>")) {
            _session.close();
            return;
        }

        // Ignore <?xml version="1.0"?> stanzas sent by clients
        if (stanza.startsWith("<?xml")) {
            return;
        }
		
        // Create DOM object from received stanza
        Element doc = reader.read(new StringReader(stanza)).getRootElement();
        if (doc == null) {
            // No document found.
            return;
        }
        String tag = doc.getName();
        if ("starttls".equals(tag)) {
            // Negotiate TLS
            if (negotiateTLS()) {
                startedTLS = true;
            }
            else {
                _connection.close();
                _session = null;
            }
        }
        else if ("auth".equals(tag)) {
            // User is trying to authenticate using SASL
            startedSASL = true;
            // Process authentication stanza
            saslStatus = SASLAuthentication.handle(_session, doc);
        }
        else if (startedSASL && "response".equals(tag)) {
            // User is responding to SASL challenge. Process response
            saslStatus = SASLAuthentication.handle(_session, doc);
        }
        else if ("compress".equals(tag)) {
            // Client is trying to initiate compression
            if (compress(doc)) {
                // Compression was successful so open a new stream and offer
                // resource binding and session establishment (to client sessions only)
                waitingCompressionACK = true;
            }
        }
        else {
            process(doc);
        }		
	}
	
    private void process(Element doc) {
        if (doc == null) {
            return;
        }

        // Ensure that connection was secured if TLS was required
        if (_connection.getTlsPolicy() == Connection.TLSPolicy.required &&
                !_connection.isSecure()) {
            closeNeverSecuredConnection();
            return;
        }

        String tag = doc.getName();
        if ("message".equals(tag)) {
            Message packet;
            try {
                packet = new Message(doc, !validateJIDs());
            }
            catch (IllegalArgumentException e) {
                _logger.debug("Rejecting packet. JID malformed", e);
                // The original packet contains a malformed JID so answer with an error.
                Message reply = new Message();
                reply.setID(doc.attributeValue("id"));
                reply.setTo(_session.getAddress());
                reply.getElement().addAttribute("from", doc.attributeValue("to"));
                reply.setError(PacketError.Condition.jid_malformed);
                _session.process(reply);
                return;
            }
            processMessage(packet);
        }
        else if ("presence".equals(tag)) {
            Presence packet;
            try {
                packet = new Presence(doc, !validateJIDs());
            }
            catch (IllegalArgumentException e) {
                _logger.debug("Rejecting packet. JID malformed", e);
                // The original packet contains a malformed JID so answer an error
                Presence reply = new Presence();
                reply.setID(doc.attributeValue("id"));
                reply.setTo(_session.getAddress());
                reply.getElement().addAttribute("from", doc.attributeValue("to"));
                reply.setError(PacketError.Condition.jid_malformed);
                _session.process(reply);
                return;
            }
            // Check that the presence type is valid. If not then assume available type
            try {
                packet.getType();
            }
            catch (IllegalArgumentException e) {
                _logger.warn("Invalid presence type", e);
                // The presence packet contains an invalid presence type so replace it with
                // an available presence type
                packet.setType(null);
            }
            // Check that the presence show is valid. If not then assume available show value
            try {
                packet.getShow();
            }
            catch (IllegalArgumentException e) {
                _logger.warn("Invalid presence show for -" + packet.toXML(), e);
                // The presence packet contains an invalid presence show so replace it with
                // an available presence show
                packet.setShow(null);
            }
            if (_session.getStatus() == Session.STATUS_CLOSED && packet.isAvailable()) {
                // Ignore available presence packets sent from a closed session. A closed
                // session may have buffered data pending to be processes so we want to ignore
                // just Presences of type available
                _logger.warn("Ignoring available presence packet of closed session: " + packet);
                return;
            }
            processPresence(packet);
        }
        else if ("iq".equals(tag)) {
            IQ packet;
            try {
                packet = getIQ(doc);
            }
            catch (IllegalArgumentException e) {
                _logger.debug("Rejecting packet. JID malformed", e);
                // The original packet contains a malformed JID so answer an error
                IQ reply = new IQ();
                if (!doc.elements().isEmpty()) {
                    reply.setChildElement(((Element)doc.elements().get(0)).createCopy());
                }
                reply.setID(doc.attributeValue("id"));
                reply.setTo(_session.getAddress());
                if (doc.attributeValue("to") != null) {
                    reply.getElement().addAttribute("from", doc.attributeValue("to"));
                }
                reply.setError(PacketError.Condition.jid_malformed);
                _session.process(reply);
                return;
            }
            if (packet.getID() == null) {
                // IQ packets MUST have an 'id' attribute so close the connection
                StreamError error = new StreamError(StreamError.Condition.invalid_xml);
                _session.deliver(error.toXML());
                _session.close();
                return;
            }
            processIQ(packet);
        }
        else {
            if (!processUnknowPacket(doc)) {
//                _logger.warn(LocaleUtils.getLocalizedString("admin.error.packet.tag") +
//                        doc.asXML());
                _session.close();
            }
        }
        
    }
    
    /**
     * Close the connection since TLS was mandatory and the entity never negotiated TLS. Before
     * closing the connection a stream error will be sent to the entity.
     */
    void closeNeverSecuredConnection() {
        // Set the not_authorized error
        StreamError error = new StreamError(StreamError.Condition.not_authorized);
        // Deliver stanza
        _connection.deliver(error.toXML());
        // Close the underlying connection
        _connection.close();
        // Log a warning so that admins can track this case from the server side
        _logger.warn("TLS was required by the server and connection was never secured. " +
                "Closing connection : " + _connection);
    }
    
	protected boolean validateRemoteDomain(Element doc, StreamID streamID){
        StringBuilder sb;
        String recipient = doc.attributeValue("to");
        String hostname = doc.attributeValue("from");
        _logger.debug("ServerDialback: RS - Received dialback key from host: " + hostname + " to: " + recipient);

        // Проверяем может ли данный сервак подключаться к bot-server
        if (!RemoteServerManager.canAccess(hostname)) {
        	_connection.deliver(new StreamError(StreamError.Condition.host_unknown).toXML());
        	_connection.close();
        	_logger.debug("ServerDialback: RS - Error, hostname is not allowed to establish a connection to this server: " + recipient);
        	return false;
        /*
         * Проверка имени, отправленному Originating Server, на соответстиве реальному имени бот-сервера
         */
        }else if (isHostUnknown(recipient)) {
        	_connection.deliver(new StreamError(StreamError.Condition.host_unknown).toXML());
        	_connection.close();
        	_logger.debug("ServerDialback: RS - Error, hostname not recognized: " + recipient);
        	return false;
        }else{
            // Check if the remote server already has a connection to the target domain/subdomain
            boolean alreadyExists = false;
        	
        }
        return false;
	}

	/**
	 * Пока этот метод просто сравнивает реальное имя бот-сервера
	 * с переданым в атрибуте "to".
	 * 
	 * В openfire реализации этот метод еще проверяет на соответствие
	 * поддомену. Однако, для бот-сервера поддомены пока не предусмотрены.
	 *  
	 * @param recipient - значение, переданое бот-серверу в атрибуте "to"
	 * @return true - если значения совпадают, false - в противном случае
	 */
    protected boolean isHostUnknown(String recipient) {
    	return recipient.equals(BotManager.BOT_SERVER_DOMAIN);
    }

    /**
     * Uses the XPP to grab the opening stream tag and create an active session
     * object. The session to create will depend on the sent namespace. In all
     * cases, the method obtains the opening stream tag, checks for errors, and
     * either creates a session or returns an error and kills the connection.
     * If the connection remains open, the XPP will be set to be ready for the
     * first packet. A call to next() should result in an START_TAG state with
     * the first packet in the stream.
     */
    protected void createSession(XmlPullParser xpp) throws XmlPullParserException, IOException {
        // Разбираем xml до корневого тега
        for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
            eventType = xpp.next();
        }

        // Check that the TO attribute of the stream header matches the server name or a valid
        // subdomain. If the value of the 'to' attribute is not valid then return a host-unknown
        // error and close the underlying connection.
        String host = xpp.getAttributeValue("", "to");
        if (validateHost() && isHostUnknown(host)) {
            StringBuilder sb = new StringBuilder(250);
            sb.append("<?xml version='1.0' encoding='");
            sb.append(CHARSET);
            sb.append("'?>");
            sb.append("<stream:stream ");
            sb.append("from=\"").append(BotManager.BOT_SERVER_DOMAIN).append("\" ");
            sb.append("id=\"").append(StringUtils.randomString(5)).append("\" ");
            sb.append("xmlns=\"").append(xpp.getNamespace(null)).append("\" ");
            sb.append("xmlns:stream=\"").append(xpp.getNamespace("stream")).append("\" ");
            sb.append("version=\"1.0\">");
        
            StreamError error = new StreamError(StreamError.Condition.host_unknown);
            sb.append(error.toXML());
            
            _connection.deliver(sb.toString());
            _connection.close();
            
            _logger.warn("Closing session due to incorrect hostname in stream header. Host: " + host +
                    ". Connection: " + _connection);
        }
        // Create the correct session based on the sent namespace. At this point the server
        // may offer the client to secure the connection. If the client decides to secure
        // the connection then a <starttls> stanza should be received
        else if (!createSession(xpp.getNamespace(null), BotManager.BOT_SERVER_DOMAIN, xpp, _connection)) {
            // No session was created because of an invalid namespace prefix so answer a stream
            // error and close the underlying connection
            StringBuilder sb = new StringBuilder(250);
            sb.append("<?xml version='1.0' encoding='");
            sb.append(CHARSET);
            sb.append("'?>");
            sb.append("<stream:stream ");
            sb.append("from=\"").append(BotManager.BOT_SERVER_DOMAIN).append("\" ");
            sb.append("id=\"").append(StringUtils.randomString(5)).append("\" ");
            sb.append("xmlns=\"").append(xpp.getNamespace(null)).append("\" ");
            sb.append("xmlns:stream=\"").append(xpp.getNamespace("stream")).append("\" ");
            sb.append("version=\"1.0\">");
            StreamError error = new StreamError(StreamError.Condition.bad_namespace_prefix);
            sb.append(error.toXML());
            _connection.deliver(sb.toString());
            _connection.close();
            _logger.warn("Closing session due to bad_namespace_prefix in stream header. Prefix: " +
                    xpp.getNamespace(null) + ". Connection: " + _connection);
        }        
    }    

    /**
     * TLS negotiation was successful so open a new stream and offer the new stream features.
     * The new stream features will include available SASL mechanisms and specific features
     * depending on the session type such as auth for Non-SASL authentication and register
     * for in-band registration.
     */
    private void tlsNegotiated() {
        // Offer stream features including SASL Mechanisms
        StringBuilder sb = new StringBuilder(620);
        sb.append(geStreamHeader());
        sb.append("<stream:features>");
        // Include available SASL Mechanisms
        sb.append(SASLAuthentication.getSASLMechanisms(_session));
        // Include specific features such as auth and register for client sessions
        String specificFeatures = _session.getAvailableStreamFeatures();
        if (specificFeatures != null) {
            sb.append(specificFeatures);
        }
        sb.append("</stream:features>");
        _connection.deliver(sb.toString());
    }

    /**
     * After SASL authentication was successful we should open a new stream and offer
     * new stream features such as resource binding and session establishment. Notice that
     * resource binding and session establishment should only be offered to clients (i.e. not
     * to servers or external components)
     */
    private void saslSuccessful() {
        StringBuilder sb = new StringBuilder(420);
        sb.append(geStreamHeader());
        sb.append("<stream:features>");

        // Include specific features such as resource binding and session establishment
        // for client sessions
        String specificFeatures = _session.getAvailableStreamFeatures();
        if (specificFeatures != null) {
            sb.append(specificFeatures);
        }
        sb.append("</stream:features>");
        _connection.deliver(sb.toString());
    }

    /**
     * After compression was successful we should open a new stream and offer
     * new stream features such as resource binding and session establishment. Notice that
     * resource binding and session establishment should only be offered to clients (i.e. not
     * to servers or external components)
     */
    private void compressionSuccessful() {
        StringBuilder sb = new StringBuilder(340);
        sb.append(geStreamHeader());
        sb.append("<stream:features>");
        // Include SASL mechanisms only if client has not been authenticated
        if (_session.getStatus() != Session.STATUS_AUTHENTICATED) {
            // Include available SASL Mechanisms
            sb.append(SASLAuthentication.getSASLMechanisms(_session));
        }
        // Include specific features such as resource binding and session establishment
        // for client sessions
        String specificFeatures = _session.getAvailableStreamFeatures();
        if (specificFeatures != null) {
            sb.append(specificFeatures);
        }
        sb.append("</stream:features>");
        _connection.deliver(sb.toString());
    }

    /**
     * Tries to secure the connection using TLS. If the connection is secured then reset
     * the parser to use the new secured reader. But if the connection failed to be secured
     * then send a <failure> stanza and close the connection.
     *
     * @return true if the connection was secured.
     */
    private boolean negotiateTLS() {
        if (_connection.getTlsPolicy() == Connection.TLSPolicy.disabled) {
            StreamError error = new StreamError(StreamError.Condition.not_authorized);
            _connection.deliver(error.toXML());
            _connection.close();
            _logger.warn("TLS requested by initiator when TLS was never offered by server. " +
                    "Closing connection : " + _connection);
            return false;
        }
        // Client requested to secure the connection using TLS. Negotiate TLS.
        try {
            startTLS();
        }
        catch (Exception e) {
            _logger.error("Error while negotiating TLS", e);
            _connection.deliver("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
            _connection.close();
            return false;
        }
        return true;
    }
    
    /**
     * Start using compression but first check if the connection can and should use compression.
     * The connection will be closed if the requested method is not supported, if the connection
     * is already using compression or if client requested to use compression but this feature
     * is disabled.
     *
     * @param doc the element sent by the client requesting compression. Compression method is
     *            included.
     * @return true if it was possible to use compression.
     */
    private boolean compress(Element doc) {
        String error = null;
        if (_connection.getCompressionPolicy() == Connection.CompressionPolicy.disabled) {
            // Client requested compression but this feature is disabled
            error = "<failure xmlns='http://jabber.org/protocol/compress'><setup-failed/></failure>";
            // Log a warning so that admins can track this case from the server side
            _logger.warn("Client requested compression while compression is disabled. Closing " +
                    "connection : " + _connection);
        }
        else if (_connection.isCompressed()) {
            // Client requested compression but connection is already compressed
            error = "<failure xmlns='http://jabber.org/protocol/compress'><setup-failed/></failure>";
            // Log a warning so that admins can track this case from the server side
            _logger.warn("Client requested compression and connection is already compressed. Closing " +
                    "connection : " + _connection);
        }
        else {
            // Check that the requested method is supported
            String method = doc.elementText("method");
            if (!"zlib".equals(method)) {
                error = "<failure xmlns='http://jabber.org/protocol/compress'><unsupported-method/></failure>";
                // Log a warning so that admins can track this case from the server side
                _logger.warn("Requested compression method is not supported: " + method +
                        ". Closing connection : " + _connection);
            }
        }

        if (error != null) {
            // Deliver stanza
            _connection.deliver(error);
            return false;
        }
        else {
            // Start using compression for incoming traffic
            _connection.addCompression();

            // Indicate client that he can proceed and compress the socket
            _connection.deliver("<compressed xmlns='http://jabber.org/protocol/compress'/>");

            // Start using compression for outgoing traffic
            _connection.startCompression();
            return true;
        }    
    }
    
    /**
     * Process the received Message packet. Registered
     * {@link org.jivesoftware.openfire.interceptor.PacketInterceptor} will be invoked before
     * and after the packet was routed.<p>
     * <p/>
     * Subclasses may redefine this method for different reasons such as modifying the sender
     * of the packet to avoid spoofing, rejecting the packet or even process the packet in
     * another thread.
     *
     * @param packet the received packet.
     */
    protected void processMessage(Message packet) {
        router.route(packet);
    }
    
    /**
     * Process the received Presence packet. Registered
     * {@link org.jivesoftware.openfire.interceptor.PacketInterceptor} will be invoked before
     * and after the packet was routed.<p>
     * <p/>
     * Subclasses may redefine this method for different reasons such as modifying the sender
     * of the packet to avoid spoofing, rejecting the packet or even process the packet in
     * another thread.
     *
     * @param packet the received packet.
     */
    protected void processPresence(Presence packet) {
        router.route(packet);
    }
    
    /**
     * Process the received IQ packet. Registered
     * {@link org.jivesoftware.openfire.interceptor.PacketInterceptor} will be invoked before
     * and after the packet was routed.<p>
     * <p/>
     * Subclasses may redefine this method for different reasons such as modifying the sender
     * of the packet to avoid spoofing, rejecting the packet or even process the packet in
     * another thread.
     *
     * @param packet the received packet.
     */
    protected void processIQ(IQ packet) {
        router.route(packet);
    }    
    private IQ getIQ(Element doc) {
        Element query = doc.element("query");
        if (query != null && "jabber:iq:roster".equals(query.getNamespaceURI())) {
//            return new Roster(doc); Ростер нам не нужен
        	return new Roster();
        }
        else {
            return new IQ(doc, !validateJIDs());
        }
    }
    
    private String geStreamHeader() {
        StringBuilder sb = new StringBuilder(200);
        sb.append("<?xml version='1.0' encoding='");
        sb.append(CHARSET);
        sb.append("'?>");
        sb.append("<stream:stream ");
        sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"");
        sb.append(getNamespace());
        sb.append("\" from=\"");
        sb.append(BotManager.BOT_SERVER_DOMAIN);
        sb.append("\" id=\"");
        sb.append(_session.getStreamID());
        sb.append("\" xml:lang=\"");
        sb.append(_connection.getLanguage());
        sb.append("\" version=\"1.0\"");
        return sb.toString();
    }
    /**
     * Returns true if the value of the 'to' attribute in the stream header should be
     * validated. If the value of the 'to' attribute is not valid then a host-unknown error
     * will be returned and the underlying connection will be closed.
     *
     * @return true if the value of the 'to' attribute in the initial stream header should be
     *         validated.
     */
    abstract boolean validateHost();

    /**
     * Creates the appropriate {@link Session} subclass based on the specified namespace.
     *
     * @param namespace the namespace sent in the stream element. eg. jabber:client.
     * @return the created session or null.
     * @throws org.xmlpull.v1.XmlPullParserException
     *
     */
    abstract boolean createSession(String namespace, String serverName, XmlPullParser xpp, Connection connection)
            throws XmlPullParserException;

    /**
     * Returns the stream namespace. (E.g. jabber:client, jabber:server, etc.).
     *
     * @return the stream namespace.
     */
    abstract String getNamespace();

    abstract void startTLS() throws Exception;
    
    /**
     * Returns true if the value of the 'to' attribute of {@link IQ}, {@link Presence} and
     * {@link Message} must be validated. Connection Managers perform their own
     * JID validation so there is no need to validate JIDs again but when clients are
     * directly connected to the server then we need to validate JIDs.
     *
     * @return rue if the value of the 'to' attribute of IQ, Presence and Messagemust be validated.
     */
    abstract boolean validateJIDs();

    /**
     * Returns true if a received packet of an unkown type (i.e. not a Message, Presence
     * or IQ) has been processed. If the packet was not processed then an exception will
     * be thrown which will make the thread to stop processing further packets.
     *
     * @param doc the DOM element of an unkown type.
     * @return true if a received packet has been processed.
     */
    abstract boolean processUnknowPacket(Element doc);

    protected static final Logger _logger = LoggerFactory.getLogger(StanzaHandlerImpl.class);
}
